/*
 * Copyright 2010 jun.enomoto<jun.enomoto@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package foursquare4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;

import foursquare4j.exception.FoursquareException;
import foursquare4j.type.Categories;
import foursquare4j.type.CheckinResult;
import foursquare4j.type.Checkins;
import foursquare4j.type.Friends;
import foursquare4j.type.Requests;
import foursquare4j.type.Response;
import foursquare4j.type.Settings;
import foursquare4j.type.Tip;
import foursquare4j.type.TipsResult;
import foursquare4j.type.User;
import foursquare4j.type.Users;
import foursquare4j.type.Venue;
import foursquare4j.type.Venues;
import foursquare4j.xml.handler.CategoriesHandler;
import foursquare4j.xml.handler.CheckinHandler;
import foursquare4j.xml.handler.CheckinsHandler;
import foursquare4j.xml.handler.FriendsHandler;
import foursquare4j.xml.handler.Handler;
import foursquare4j.xml.handler.HistoryHandler;
import foursquare4j.xml.handler.RequestsHandler;
import foursquare4j.xml.handler.ResponseHandler;
import foursquare4j.xml.handler.SettingsHandler;
import foursquare4j.xml.handler.TipHandler;
import foursquare4j.xml.handler.TipsResultHandler;
import foursquare4j.xml.handler.UserHandler;
import foursquare4j.xml.handler.UsersHandler;
import foursquare4j.xml.handler.VenueHandler;
import foursquare4j.xml.handler.VenuesHandler;

public abstract class FoursquareBase implements Foursquare {

	private static final Logger logger = Logger.getLogger(FoursquareBase.class.getName());

	protected static enum HttpMethod {
		POST, GET
	}

	protected String userAgent = Foursquare.USER_AGENT;

	public void setUserAgent(final String userAgent) {
		this.userAgent = userAgent;
	}

	public Checkins checkins(final String geolat, final String geolong) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.GET, Foursquare.API_CHECKINS_URL, parameters, new CheckinsHandler());
	}

	public CheckinResult checkin(final String vid, final String venue, final String shout, final String _private, final String twitter, final String facebook, final String geolat, final String geolong) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("vid", vid);
		parameters.put("venue", venue);
		parameters.put("shout", shout);
		parameters.put("private", _private);
		parameters.put("twitter", twitter);
		parameters.put("facebook", facebook);
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.POST, Foursquare.API_CHECKIN_URL, parameters, new CheckinHandler());
	}

	public Checkins history(final String l, final String sinceid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("l", l);
		parameters.put("sinceid", sinceid);
		return execute(HttpMethod.GET, Foursquare.API_HISTORY_URL, parameters, new HistoryHandler());
	}

	public User user(final String uid, final String badges, final String mayor) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("uid", uid);
		parameters.put("badges", badges);
		parameters.put("mayor", mayor);
		return execute(HttpMethod.GET, Foursquare.API_USER_URL, parameters, new UserHandler());
	}

	public Friends friends(final String uid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("uid", uid);
		return execute(HttpMethod.GET, Foursquare.API_FRIENDS_URL, parameters, new FriendsHandler());
	}

	public Venues venues(final String geolat, final String geolong, final String l, final String q) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		parameters.put("l", l);
		parameters.put("q", q);
		return execute(HttpMethod.GET, Foursquare.API_VENUES_URL, parameters, new VenuesHandler());
	}

	public Venue venue(final String vid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("vid", vid);
		return execute(HttpMethod.GET, Foursquare.API_VENUE_URL, parameters, new VenueHandler());
	}

	public Categories categories(final String categories, final String id, final String fullpathname, final String nodename) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("categories", categories);
		parameters.put("id", id);
		parameters.put("fullpathname", fullpathname);
		parameters.put("nodename", nodename);
		return execute(HttpMethod.GET, Foursquare.API_CATEGORIES_URL, parameters, new CategoriesHandler());
	}

	public Venue addVenue(final String name, final String address, final String crossstreet, final String city, final String state, final String zip, final String phone, final String geolat, final String geolong, final String primarycategoryid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("name", name);
		parameters.put("address", address);
		parameters.put("crossstreet", crossstreet);
		parameters.put("city", city);
		parameters.put("state", state);
		parameters.put("zip", zip);
		parameters.put("phone", phone);
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		parameters.put("primarycategoryid", primarycategoryid);
		return execute(HttpMethod.POST, Foursquare.API_ADDVENUE_URL, parameters, new VenueHandler());
	}

	public Response proposeVenueEdit(final String vid, final String name, final String address, final String crossstreet, final String city, final String state, final String zip, final String phone, final String geolat, final String geolong) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("vid", vid);
		parameters.put("name", name);
		parameters.put("address", address);
		parameters.put("crossstreet", crossstreet);
		parameters.put("city", city);
		parameters.put("state", state);
		parameters.put("zip", zip);
		parameters.put("phone", phone);
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.POST, Foursquare.API_PROPOSEEDIT_URL, parameters, new ResponseHandler());
	}

	public Response flagVenueAsClosed(final String vid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("vid", vid);
		return execute(HttpMethod.POST, Foursquare.API_FLAGCLOSED_URL, parameters, new ResponseHandler());
	}

	public TipsResult tips(final String geolat, final String geolong, final String l) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		parameters.put("l", l);
		return execute(HttpMethod.GET, Foursquare.API_TIPS_URL, parameters, new TipsResultHandler());
	}

	public Tip addTip(final String vid, final String text, final String geolat, final String geolong) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("vid", vid);
		parameters.put("text", text);
		parameters.put("type", "tip");
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.POST, Foursquare.API_ADDTIP_URL, parameters, new TipHandler());
	}

	public Tip addTodo(final String vid, final String text, final String geolat, final String geolong) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("vid", vid);
		parameters.put("text", text);
		parameters.put("type", "todo");
		parameters.put("geolat", geolat);
		parameters.put("geolong", geolong);
		return execute(HttpMethod.POST, Foursquare.API_ADDTIP_URL, parameters, new TipHandler());
	}

	public Tip markTipAsTodo(final String tid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("tid", tid);
		return execute(HttpMethod.POST, Foursquare.API_TIP_MARKTODO_URL, parameters, new TipHandler());
	}

	public Tip markTipAsDone(final String tid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("tid", tid);
		return execute(HttpMethod.POST, Foursquare.API_TIP_MARKDONE_URL, parameters, new TipHandler());
	}

	public Tip unmarkTip(final String tid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("tid", tid);
		return execute(HttpMethod.POST, Foursquare.API_TIP_UNMARK_URL, parameters, new TipHandler());
	}

	public Requests pendingFriendRequests() throws FoursquareException {
		return execute(HttpMethod.GET, Foursquare.API_FRIEND_REQUESTS_URL, new Parameters(), new RequestsHandler());
	}

	public User approveFriendRequest(final String uid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("uid", uid);
		return execute(HttpMethod.POST, Foursquare.API_FRIEND_APPROVE_URL, parameters, new UserHandler());
	}

	public User denyFriendRequest(final String uid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("uid", uid);
		return execute(HttpMethod.POST, Foursquare.API_FRIEND_DENY_URL, parameters, new UserHandler());
	}

	public User sendFriendRequest(final String uid) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("uid", uid);
		return execute(HttpMethod.POST, Foursquare.API_FRIEND_SENDREQUEST_URL, parameters, new UserHandler());
	}

	public Users findFriendsByName(final String q) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("q", q);
		return execute(HttpMethod.GET, Foursquare.API_FINDFRIENDS_BYNAME_URL, parameters, new UsersHandler());
	}

	public Users findFriendsByPhone(final String q) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("q", q);
		return execute(HttpMethod.GET, Foursquare.API_FINDFRIENDS_BYPHONE_URL, parameters, new UsersHandler());
	}

	public Users findFriendsByTwitter(final String q) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put("q", q);
		return execute(HttpMethod.GET, Foursquare.API_FINDFRIENDS_BYTWITTER_URL, parameters, new UsersHandler());
	}

	public Settings setPings(final String uid, final String pings) throws FoursquareException {
		final Parameters parameters = new Parameters();
		parameters.put(uid, pings);
		return execute(HttpMethod.POST, Foursquare.API_SETTINGS_SETPINGS_URL, parameters, new SettingsHandler());
	}

	protected abstract <T> T execute(final HttpMethod method, final String url, final Parameters parameters, final Handler<T> handler) throws FoursquareException;

	protected <T> T parseBody(InputStream body, final Handler<T> handler) throws ParserConfigurationException, SAXException, IOException {
		if (body == null) return null;

		if (logger.isLoggable(Level.FINE)) {
			final ByteArrayOutputStream baos = new ByteArrayOutputStream();
			final byte[] buff = new byte[1024];
			int size = 0;
			while ((size = body.read(buff, 0, buff.length)) > -1)
				baos.write(buff, 0, size);

			final byte[] data = baos.toByteArray();
			logger.fine(new String(data, "UTF-8"));
			body = new ByteArrayInputStream(data);
		}

		final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
		parser.parse(body, handler);
		return handler.getObject();
	}

	protected static class Parameters extends LinkedHashMap<String, String> {

		private static final long serialVersionUID = 1L;

		@Override
		public String put(final String key, final String value) {
			if (value == null) return null;
			return super.put(key, value);
		}
	}
}